
package io;

import domain.ArchitectureDTO;
import domain.LayerDTO;
import domain.StructureDTO;
import domain.DomainFacade;
import domain.DomainFacadeImpl;
import domain.Layer;
import domain.UnitDTO;
import java.io.File;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XMLWriter implements Writer {

    private DomainFacade facade;
    private ArchitectureDTO architecture;
    private StructureDTO structure;
    
    private Document dom;

    public XMLWriter() {
        this.facade = DomainFacadeImpl.getInstance();
    }

    @Override
    public void writeArchitectureFile(File file) {
        this.architecture = facade.getArchitecture();
        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            this.dom = documentBuilder.newDocument();
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(XMLWriter.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.createArchitectureDOM();
        Source source = new DOMSource(dom);

        Result result = new StreamResult(file);
        try {
            Transformer xformer = TransformerFactory.newInstance().newTransformer();
            xformer.setOutputProperty(OutputKeys.INDENT, "yes");
            xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            try {
                xformer.transform(source, result);
            } catch (TransformerException ex) {
                Logger.getLogger(XMLWriter.class.getName()).log(Level.SEVERE, null, ex);
            }
        } catch (TransformerConfigurationException ex) {
            Logger.getLogger(XMLWriter.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public void writeStructureFile(File file) {
        this.architecture = facade.getArchitecture();
        this.structure = facade.getStructure();
        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            this.dom = documentBuilder.newDocument();
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(XMLWriter.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.createStructureDOM();
        Source source = new DOMSource(dom);

        Result result = new StreamResult(file);
        try {
            Transformer xformer = TransformerFactory.newInstance().newTransformer();
            xformer.setOutputProperty(OutputKeys.INDENT, "yes");
            xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            try {
                xformer.transform(source, result);
            } catch (TransformerException ex) {
                Logger.getLogger(XMLWriter.class.getName()).log(Level.SEVERE, null, ex);
            }
        } catch (TransformerConfigurationException ex) {
            Logger.getLogger(XMLWriter.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void createStructureDOM() {
        Element root = dom.createElement("structureDefinition");
        dom.appendChild(root);

        this.addStructureName();
        this.addStructureAuthor();
        this.addStructureLayers();
        this.addStructureUnits();
    }

    private void createArchitectureDOM() {
        Element root = dom.createElement("architectureDefinition");
        dom.appendChild(root);

        this.addArchitectureName();
        this.addArchitectureAuthor();
        this.addArchitectureLayers();
        this.addArchitectureAllowedLayers();
    }
    
    private void addArchitectureName() {
        Element root = (Element) dom.getFirstChild();
        
        if (root != null) {
            Element element = dom.createElement("name");
            element.setAttribute("value", architecture.getName());
            root.appendChild(element);
        }
    }
    
    private void addArchitectureAuthor() {
        Element root = (Element) dom.getFirstChild();
        
        if (root != null) {
            Element element = dom.createElement("author");
            element.setAttribute("value", architecture.getAuthor());
            root.appendChild(element);
        }
    }
    
    private void addStructureName() {
        Element root = (Element) dom.getFirstChild();
        
        if(root != null){
            Element element = dom.createElement("name");
            element.setAttribute("value", structure.getName());
            root.appendChild(element);
        }
    }
    
    private void addStructureAuthor() {
        Element root = (Element) dom.getFirstChild();
        
        if(root != null){
            Element element = dom.createElement("author");
            element.setAttribute("value", structure.getAuthor());
            root.appendChild(element);
        }
    }

    private void addArchitectureAllowedLayers() {
        NodeList layerElements = dom.getElementsByTagName("layer");

        for (int i = 0; i < layerElements.getLength(); i++) {
            Node layerElement = layerElements.item(i);
            Map<String, LayerDTO> allowedLayersForLayer = architecture.getLayers().get(layerElement.getAttributes().getNamedItem("name").getNodeValue()).getAllowedLayers();
            if (allowedLayersForLayer.values().size() > 0) {
                Element allowedLayerElement = dom.createElement("allowedToAccess");
                for (LayerDTO layer : allowedLayersForLayer.values()) {
                    Element accessLayer = dom.createElement("accessLayer");
                    accessLayer.setAttribute("name", layer.getName());
                    allowedLayerElement.appendChild(accessLayer);
                }
                layerElement.appendChild(allowedLayerElement);
            }
        }
    }

    private void addArchitectureLayers() {
        Map<String, LayerDTO> layers = architecture.getLayers();

        Element root = (Element) dom.getFirstChild();

        if (root != null) {
            for (LayerDTO layer : layers.values()) {
                Element layerElement = dom.createElement("layer");
                layerElement.setAttribute("name", layer.getName());
                layerElement.setAttribute("number", Integer.toString(layer.getNumber()));

                root.appendChild(layerElement);
            }
        }
    }

    private void addStructureUnits() {
        NodeList layerElements = dom.getElementsByTagName("layer");

        for (int i = 0; i < layerElements.getLength(); i++) {
            Node layerElement = layerElements.item(i);
            
            System.out.println(layerElement.getAttributes().getNamedItem("name").getNodeValue());
            Set<UnitDTO> units = architecture.getLayers().get(layerElement.getAttributes().getNamedItem("name").getNodeValue()).getUnits();
            
            for (UnitDTO unit : units) {
                if (unit.getType().getName().equals("PACKAGE")) {
                    Element packageElement = dom.createElement("package");
                    packageElement.setAttribute("name", unit.getName());
                    layerElement.appendChild(packageElement);
                } else if (unit.getType().getName().equals("CLASS")) {
                    Element classElement = dom.createElement("class");
                    classElement.setAttribute("name", unit.getName());
                    layerElement.appendChild(classElement);
                }
            }
        }
    }

    private void addStructureLayers() {
        Map<String, Layer> layerMap = this.facade.getLayerMap();

        Element root = (Element) dom.getFirstChild();

        if (root != null) {
            for (Layer layer : layerMap.values()) {
                Element layerElement = dom.createElement("layer");
                layerElement.setAttribute("name", layer.getName());

                root.appendChild(layerElement);
            }
        }
    }
}